package org.simtube.query;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Properties;
import java.util.Scanner;

import org.simtube.cluster.Clustering;
import org.simtube.feedback.Feedback;
import org.simtube.index.CSRTreeIndex;
import org.simtube.index.Index;
import org.simtube.index.QuickIndex;
import org.simtube.index.Region;
import org.simtube.lsi.LSIConvertor;
import org.simtube.preprocess.FilePreprocess;
import org.simtube.util.Cluster;
import org.simtube.util.DocItem;
import org.simtube.util.DocVector;
import org.simtube.util.DocWithId;

public class QueryController {
	
	// fields
	
	private Properties prop;
	private String corpusFolder;
	private String dataFolder;
	private String docMatFile;
	private String indexFile;
	private String tfFolder;
	private String termFile;
	private String vfile;
	private String ufile;
	private String sfile;
	
	private static int CLUSTER_FILE_NUMBER = 200;
	private Index index;
	private DocItem[] docMat;
	private FilePreprocess filePreprocess; 
	private LSIConvertor lsiConvertor;
	private Feedback feedback;
	private Clustering cluster;
	
	private DocVector queryVector; //当前查询的特征向量
	private int[] docIds; //当前查询结果
	// setters and getters
	
	public void setDocMat(DocItem[] docMat) {
		this.docMat = docMat;
	}

	public DocItem[] getDocMat() {
		return docMat;
	}

	public void setDocId(int[] docId){
		this.docIds = docId;
	}
	public void setIndex(Index index) {
		this.index = index;
	}

	public Index getIndex() {
		return index;
	}

	public void setDataFolder(String dataFolder) {
		this.dataFolder = dataFolder;
	}

	public String getDataFolder() {
		return dataFolder;
	}

	public void setCorpusFolder(String corpusFolder) {
		this.corpusFolder = corpusFolder;
	}

	public String getCorpusFolder() {
		return corpusFolder;
	}

	public void setProp(Properties prop) {
		this.prop = prop;
	}

	public Properties getProp() {
		return prop;
	}
	
	public FilePreprocess getFilePreprocess(){
		return filePreprocess;
	}
	public String getTermById(int termId){
		return filePreprocess.getTermById(termId);
	}
	/**
	 * 根据文件在docMat中的索引值得到文件的内容
	 * @param id
	 * @return
	 */
	public String getDocTextById(int id){
		return filePreprocess.getDocText(docMat[id].index);
	}
	/**
	 * Default constructor, all initialization will be done here in this method
	 */
	public QueryController() {
		filePreprocess = new FilePreprocess();
		lsiConvertor = new LSIConvertor();
		feedback = new Feedback();
		cluster = new Clustering(filePreprocess);
	}
	
	/**
	 * File loading and module initialization will be done here
	 */
	public void init() {
		prop = new Properties();
		try {
			prop.load(new FileInputStream("simtube.cfg"));
		} catch (FileNotFoundException e) {
			System.err.println("Configuration file not found!");
			e.printStackTrace();
		} catch (IOException e) {
			System.err.println("Error when reading simtube.cfg");
			e.printStackTrace();
		}
		
		prop.list(System.out);
		
		if(prop.containsKey("path")) {
			System.out.println(prop.getProperty("path"));
		}
		
		if(prop.containsKey("corpusFolder")) {
			corpusFolder = prop.getProperty("corpusFolder");
		} else {
			System.err.println("Cannot find corpus folder in QueryController.java...");
			//return;
		}
		
		if(prop.containsKey("dataFolder")) {
			dataFolder = prop.getProperty("dataFolder");
		} else {
			System.err.println("Cannot find data folder in QueryController.java...");
			return;
		}
		
		if(prop.containsKey("tfFolder")){
			tfFolder = prop.getProperty("tfFolder");
		} else{
			System.err.println("Cannot find term frequency folder in QueryController.java...");
			//return;
		}
		
		if(prop.containsKey("termFile")){
			termFile = prop.getProperty("termFile");
		} else{
			System.err.println("Cannot find term file in QueryController.java...");
			return;
		}
		
		if(prop.containsKey("indexFile")) {
			indexFile = prop.getProperty("indexFile");
		} else {
			indexFile = "index.dat";
		}
		
		if(prop.containsKey("uFile")){
			ufile = prop.getProperty("uFile");
		}
		else{
			System.err.println("Cannot find uFile in QueryController.java...");
			return;
		}
		
		if(prop.containsKey("vFile")){
			vfile = prop.getProperty("vFile");
		} else{
			System.err.println("Cannot find vFile in QueryController.java...");
			return;
		}
		
		if(prop.containsKey("sFile")){
			sfile = prop.getProperty("sFile");
		} else{
			System.err.println("Cannot find sFile in QueryController.java...");
			return;
		}
		
		if(prop.containsKey("docMatFile")) {
			docMatFile = prop.getProperty("docMatFile");
		} else {
			docMatFile = "docMat.dat";
		}
		
		System.out.println("============== load SVD ==============");
		try{
			lsiConvertor.loadSVD(ufile, sfile, vfile);
		}
		catch(IOException e){
			e.printStackTrace();
		}
		System.out.println("============== generate DocMat ==============");
		docMat = lsiConvertor.generateDocMat(dataFolder);
		
		if(prop.containsKey("CSRTreeIndex")) {
			boolean csrtree = Boolean.parseBoolean(prop.getProperty("CSRTreeIndex"));
			if(csrtree) index = new CSRTreeIndex(docMat);
			else index = new QuickIndex(docMat);
		} else {
			index = new QuickIndex(docMat);
		}
		
		// index.setDocMat(docMat);
		
		System.out.println("============== bulid index ==============");
		if(prop.containsKey("loadIndex")) {
			boolean loadIndex = Boolean.parseBoolean(prop.getProperty("loadIndex"));
			if(loadIndex) index.loadIndex(indexFile);
			else index.buildIndex();
		} else {
			index.buildIndex();
		}
		
		filePreprocess.readTermsFromFile(termFile);
		
		feedback.setDvectors(docMat);
		cluster.setDvectors(docMat);
		System.out.println("============== finish initiazing ==============");
	}
	
	/**
	 * The document matrix will be loaded
	 */
	private void loadDocMat() {
		Scanner s = new Scanner(dataFolder + "\\" + docMatFile);
		int ndoc = s.nextInt();
		int ndim = s.nextInt();
		double[] v = null;
		
		docMat = new DocItem[ndoc];
		
		for(int i = 0; i < ndoc; i++) {
			v = new double[ndim];
			for(int j = 0; j < ndim; j++)
				v[j] = s.nextDouble();
			docMat[i] = new DocItem(new DocVector(v), i);
		}
		s.close();
	}
	/**
	 * 返回与一个字符串最相似的前K个结果（界面调用，一般取10）
	 * 实际查询的时候，查的是200个
	 * @param k
	 * @param str
	 * @return
	 */
	public DocWithId[] kNNQuery(int k, String str){
		queryVector = lsiConvertor.foldIn(filePreprocess.getStringFeature(str));
		int[] docId = index.kNNQuery(CLUSTER_FILE_NUMBER, queryVector);
		setDocId(docId);
		
		System.out.println("Query result: ");
		
		int id;
		double distance;
		DocWithId[] docs = new DocWithId[k];
		for(int i = 0; i < k; i++){
			id = docMat[docId[i]].index;
			distance = Region.distance(new Region(queryVector.normalize(), 0),
					new Region(docMat[docId[i]].vector, 0));
			docs[i] = new DocWithId(filePreprocess.getDocText(id), id, distance);
			// System.out.println("File "+id + " "+distance);
		}
		return docs;
	}
	/**
	 * 返回与一个文件最相似的前K个结果（测试时被调用）
	 * @param k
	 * @param fileName
	 * @return
	 */
	public DocWithId[] fileQuery(int k, String fileName){
		queryVector = lsiConvertor.foldIn(filePreprocess.getDocFeature(fileName));
		int[] docId = index.kNNQuery(CLUSTER_FILE_NUMBER, queryVector);
		setDocId(docId);
		/*
		int j = filePreprocess.getDocIndexById(Integer.parseInt((new File(fileName)).getName()));
		System.out.println(fileName + ": " + Region.distance(
				new Region(queryVector, 0), new Region(docMat[j].vector, 0)));
		
		System.out.println("Query result: ");
		*/
		int id;
		double distance;
		DocWithId[] docs = new DocWithId[k];
		for(int i = 0; i < k; i++){
			id = docMat[docId[i]].index;
			distance = Region.distance(new Region(queryVector.normalize(), 0),
					new Region(docMat[docId[i]].vector, 0));
			docs[i] = new DocWithId(filePreprocess.getDocText(id), id, distance);
			docs[i].setDocMatId(docId[i]);
			//System.out.println("File "+id + " "+distance);
		}
		return docs;
	}
	/**
	 * 用户反馈查询
	 * @param k 查询的结果集大小：k个结果
	 * @param dr 标注为相关的文档数组
	 * @param dn 标注为不相关的文档数组
	 * @return
	 */
	public DocWithId[] feedbackQuery(int k, int[] dr, int[] dn){
		if(queryVector == null){
			return null;
		}
		DocVector newq;
		newq = feedback.getQuery(queryVector, dr, dn);
		int[] docId = index.kNNQuery(CLUSTER_FILE_NUMBER, newq);
		setDocId(docId);
		/*
		System.out.println("Query result: ");
		System.out.println("" + Region.distance(new Region(newq.normalize(), 0),
					new Region(queryVector.normalize(), 0)));
		*/
		
		int id;
		double distance;
		DocWithId[] docs = new DocWithId[k];
		for(int i = 0; i < k; i++){
			id = docMat[docId[i]].index;
			distance = Region.distance(new Region(newq.normalize(), 0),
					new Region(docMat[docId[i]].vector, 0));
			docs[i] = new DocWithId(filePreprocess.getDocText(id), id, distance);
			docs[i].setDocMatId(docId[i]);
			/*
			System.out.println("File "+id + " "+distance+ " "+
					Region.distance(new Region(queryVector.normalize(), 0),
							new Region(docMat[docId[i]].vector, 0)));
							*/
		}
		queryVector = newq;
		return docs;
	}
	/**
	 * number of clusters
	 * @param k
	 * @return
	 */
	public ArrayList<Cluster> clusterQuery(int k){
		return cluster.cluster(docIds, k);
	}
	/**
	 * 测试查询的速度（查询给定文件夹里前fileNumber个文件）
	 * @param fileNumber 查询的文件数目
	 * @param dataDir 查询文件所在的文件夹
	 * @param resultFile 结果文件
	 */
	public void test_querySpeed(int fileNumber, String dataDir, String resultFile){
		Date start = new Date();
		File dataPath = new File(dataDir);
		if(!dataPath.exists()){
			System.out.println("Directory "+dataDir+" doesn't exist.");
			return;
		}
		File[] files = dataPath.listFiles();
		
		File resultPath = (new File(resultFile)).getParentFile();
		if(!resultPath.exists()){
			resultPath.mkdirs();
		}
		try{
			BufferedWriter bw = new BufferedWriter(new FileWriter(resultFile));
			
			int size;
			size = (fileNumber < files.length ? fileNumber : files.length);
			DocWithId[] docs;
			for(int i = 0; i < size; i++){
				System.out.println("================== "+i+" :query "+files[i].getName()+" start ====================");
				docs = fileQuery(10, files[i].getAbsolutePath());
				bw.write(""+files[i].getName());
				for(int j = 0; j < docs.length; j++){
					bw.write(" "+docs[j].getId());
				}
				bw.newLine();
			}
			bw.close();
			
			Date end = new Date();
			System.out.println("The total query time is "+(end.getTime() - start.getTime()));
			System.out.println("The average query time is "+((end.getTime() - start.getTime())/size));
		}
		catch(IOException e){
			e.printStackTrace();
		}
	}
	
	public void testBench() {
		String inputPath = "D:/2010 Spring/Advanced Data Structure/projects/query";
		File folder = new File(inputPath);
		File[] flist = folder.listFiles();
		
		int[] fname = new int[flist.length];
		for(int i = 0; i < fname.length; i++) {
			fname[i] = Integer.parseInt(flist[i].getName().replace(".query", ""));
			System.out.println(fname[i]);
		}
		
		Arrays.sort(fname);
		
		for(int i = 0; i < fname.length; i++)
			System.out.println(fname[i]);
		
		File finput = null;
		
		BufferedReader r = null;
		String line = null;
		String query = null;
		DocWithId[] rList = null;
		
		PrintWriter w = null;
		try {
			w = new PrintWriter("D:/result.list");
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
			return;
		}
		
		for(int i = 0; i < fname.length; i++) {
			try {
				System.out.println("Searching for " + (i + 1) + "th file: "
						+ fname[i]);
				
				finput = new File(inputPath + "/" + fname[i] + ".query");
				r = new BufferedReader(new FileReader(finput));
				query = "";
				while((line = r.readLine()) != null)
					query += line + "\n";
				r.close();
				
				rList = this.kNNQuery(10, query);
				
				int j = 0;
				for(j = 0; j < rList.length - 1; j++)
					w.print(rList[j].getId() + ",");
				w.println(rList[j].getId());
				w.flush();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		w.close();
	}
	/**
	 * 聚类测试
	 */
	public void test_clusterQuery(int k){
		kNNQuery(10, "I love banking, I dream to be a banker");
		int[] docs;
		Cluster temp;
		ArrayList<Cluster> clusterResult = clusterQuery(k);
		for(int i = 0; i < clusterResult.size(); i++){
			temp = clusterResult.get(i);
			docs = temp.getDocItems();
			System.out.println("cluster "+temp.getTag());
			for(int j = 0; j < docs.length; j++){
				System.out.print(" "+docMat[docs[j]].index);
			}
			System.out.println();
		}
	}
	/**
	 * feedback测试
	 */
	public void test_feedback(){
		DocWithId[] queryResult = fileQuery(30, "data/text/1");
		
		int[] dr = new int[queryResult.length/2];
		int[] dn = new int[queryResult.length - queryResult.length/2];
		for(int i = 0; i < dr.length; i++){
			dr[i] = queryResult[i].getDocMatId();
		}
		for(int i = 0; i < dn.length; i++){
			dn[i] = queryResult[i+queryResult.length/2].getDocMatId();
		}
		queryResult = feedbackQuery(30, dr, dn);
		
		for(int i = 0; i < queryResult.length; i++){
			System.out.println(queryResult[i]);
		}
	}
	
	public static void main(String[] args) {
		QueryController qc = new QueryController();
		Date start = new Date();
		qc.init();
		Date end = new Date();
		System.out.println("The initialization time is "+(end.getTime() - start.getTime()));
		
		qc.testBench();
		// qc.test_clusterQuery(10);
		//qc.test_querySpeed(5000, "data/text/", "data/5000_rtree.txt");
	}
}
